/**
 * webserver
 * - 支持sse
 */
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <esp_log.h>
#include <sys/param.h>

#include <esp_http_server.h>
#include "esp_event.h"
#include "esp_check.h"
// #include <time.h>
// #include <sys/time.h>
#include <esp_system.h>
#include "freertos/semphr.h"
#include "cJSON.h"

#include "webserver.h"

static const char *TAG = "webserver";

// -----------web页面----------------
// 使用嵌入web页面文件
extern const uint8_t index_html_start[] asm("_binary_index_html_start");
extern const uint8_t index_html_end[] asm("_binary_index_html_end");
extern const uint8_t style_css_start[] asm("_binary_style_css_start");
extern const uint8_t style_css_end[] asm("_binary_style_css_end");
extern const uint8_t script_js_start[] asm("_binary_script_js_start");
extern const uint8_t script_js_end[] asm("_binary_script_js_end");
extern const uint8_t favicon_ico_start[] asm("_binary_favicon_ico_start");
extern const uint8_t favicon_ico_end[] asm("_binary_favicon_ico_end");

struct embedded_file
{
    const char *path;
    const uint8_t *start;
    const uint8_t *end;
    const char *mime_type;
};

static const struct embedded_file embedded_files[] = {
    {"/index.html", index_html_start, index_html_end, "text/html"},
    {"/style.css", style_css_start, style_css_end, "text/css"},
    {"/script.js", script_js_start, script_js_end, "application/javascript"},
    {"/favicon.ico", favicon_ico_start, favicon_ico_end, "image/x-icon"},
};
// ---------------------------

static httpd_handle_t server_handle = NULL;

static QueueHandle_t sse_data_queue = NULL;
static bool sse_client_connected = false;
static SemaphoreHandle_t sse_state_mutex;

static SemaphoreHandle_t worker_ready_count;
static QueueHandle_t request_queue;
static TaskHandle_t worker_handle;

typedef esp_err_t (*httpd_req_handler_t)(httpd_req_t *req);

typedef struct
{
    httpd_req_t *req;
    httpd_req_handler_t handler;
} httpd_async_req_t;

static esp_err_t sse_async(httpd_req_t *req)
{
    // 检查并设置连接状态
    xSemaphoreTake(sse_state_mutex, portMAX_DELAY);
    if (sse_client_connected)
    {
        xSemaphoreGive(sse_state_mutex);
        ESP_LOGW(TAG, "SSE client already connected. Rejecting new connection.");
        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Service Unavailable");
        return ESP_FAIL;
    }
    sse_client_connected = true;
    xSemaphoreGive(sse_state_mutex);

    ESP_LOGI(TAG, "SSE client connected. Handler starting processing loop.");

    // 设置响应头
    httpd_resp_set_type(req, "text/event-stream");
    httpd_resp_set_hdr(req, "Cache-Control", "no-cache");
    httpd_resp_set_hdr(req, "Connection", "keep-alive");

    // 发送欢迎信息
    const char *welcome_msg = "event: info\ndata: Welcome!\n\n";
    httpd_resp_send_chunk(req, welcome_msg, strlen(welcome_msg));

    while (1)
    {
        webserver_httpd_async_req_t async_req = {0};
        if (xQueueReceive(sse_data_queue, &async_req, portMAX_DELAY))
        {
            if (async_req.cmd == 0xFF)
            {
                ESP_LOGI(TAG, "Received shutdown signal. Closing SSE connection.");
                break;
            }
            else if (async_req.cmd == 0x01)
            {
                if (httpd_resp_send_chunk(req, async_req.data, async_req.len) != ESP_OK)
                {
                    ESP_LOGE(TAG, "Failed to send chunk, client disconnected.");
                    break;
                }
            }
        }
    }

    // 清理连接状态
    xSemaphoreTake(sse_state_mutex, portMAX_DELAY);
    sse_client_connected = false;
    xSemaphoreGive(sse_state_mutex);

    // 结束会话
    httpd_resp_send_chunk(req, NULL, 0);
    ESP_LOGI(TAG, "SSE client disconnected. Handler task unblocking and finishing.");
    return ESP_OK;
}

static esp_err_t queue_request(httpd_req_t *req, httpd_req_handler_t handler)
{
    httpd_req_t *copy = NULL;
    esp_err_t err = httpd_req_async_handler_begin(req, &copy);
    if (err != ESP_OK)
    {
        return err;
    }

    httpd_async_req_t async_req = {
        .req = copy,
        .handler = handler,
    };

    int ticks = 0;
    if (xSemaphoreTake(worker_ready_count, ticks) == false)
    {
        ESP_LOGE(TAG, "No workers are available");
        httpd_req_async_handler_complete(copy); // cleanup
        return ESP_FAIL;
    }

    if (xQueueSend(request_queue, &async_req, pdMS_TO_TICKS(100)) == false)
    {
        ESP_LOGE(TAG, "worker queue is full");
        httpd_req_async_handler_complete(copy); // cleanup
        return ESP_FAIL;
    }

    return ESP_OK;
}

/**
 * get/post api接口匹配
 */
static esp_err_t api_handler(httpd_req_t *req)
{
    // 获取 URI
    const char *uri = req->uri;
    ESP_LOGI(TAG, "[API] Received request: %s %s", http_method_str(req->method), uri);

    // 提取子路径（去掉 /api/v1/ 前缀）
    const char *prefix = "/api/v1/";
    if (strncmp(uri, prefix, strlen(prefix)) != 0)
    {
        httpd_resp_send_404(req);
        return ESP_FAIL;
    }
    const char *subpath = uri + strlen(prefix); // 提取 ctrl, network, service 等

    // 接收post数据
    char content[256];
    size_t recv_size = MIN(req->content_len, sizeof(content) - 1);
    if (recv_size > 0)
    {
        int ret = httpd_req_recv(req, content, recv_size);
        if (ret <= 0)
        {
            httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Failed to read request body");
            return ESP_FAIL;
        }
        content[ret] = '\0';
    }
    else
    {
        content[0] = '\0';
    }

    // 响应
    cJSON *response = cJSON_CreateObject();
    cJSON_AddStringToObject(response, "status", "success");

    if (strcmp(subpath, "ctrl") == 0)
    {
        if (req->method == HTTP_GET)
        {
            cJSON *data = cJSON_CreateObject();
            cJSON_AddNumberToObject(data, "rly", 1);
            cJSON_AddItemToObject(response, "data", data);
        }
        else if (req->method == HTTP_POST)
        {
            cJSON *req_json = cJSON_Parse(content);
            if (req_json && cJSON_GetObjectItem(req_json, "cmd"))
            {
                const char *cmd = cJSON_GetObjectItem(req_json, "cmd")->valuestring;
                if (strcmp(cmd, "toggle_relay") == 0)
                {
                    ESP_LOGI(TAG, "Toggling relay");
                    cJSON_AddStringToObject(response, "message", "Relay toggled");
                }
                else
                {
                    cJSON_Delete(response);
                    cJSON_Delete(req_json);
                    httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid command");
                    return ESP_FAIL;
                }
                cJSON_Delete(req_json);
            }
            else
            {
                cJSON_Delete(response);
                httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
                return ESP_FAIL;
            }
        }
        else
        {
            cJSON_Delete(response);
            httpd_resp_send_err(req, HTTPD_405_METHOD_NOT_ALLOWED, "Method not allowed");
            return ESP_FAIL;
        }
    }
    else if (strcmp(subpath, "status") == 0)
    {
    }
    else if (strcmp(subpath, "network") == 0)
    {
        if (req->method == HTTP_GET)
        {
            cJSON *data = cJSON_CreateObject();
            cJSON_AddNumberToObject(data, "adp", 1);
            cJSON_AddStringToObject(data, "ip", "192.168.1.100");
            cJSON_AddNumberToObject(data, "sig", -65);
            cJSON_AddNumberToObject(data, "con", 1);
            cJSON_AddItemToObject(response, "data", data);
        }
        else
        {
            cJSON_Delete(response);
            httpd_resp_send_err(req, HTTPD_405_METHOD_NOT_ALLOWED, "Method not allowed");
            return ESP_FAIL;
        }
    }
    else if (strcmp(subpath, "service") == 0)
    {
        if (req->method == HTTP_GET)
        {
            cJSON *data = cJSON_CreateObject();
            cJSON_AddNumberToObject(data, "typ", 0);
            cJSON_AddNumberToObject(data, "prt", 8080);
            cJSON_AddItemToObject(response, "data", data);
        }
        else
        {
            cJSON_Delete(response);
            httpd_resp_send_err(req, HTTPD_405_METHOD_NOT_ALLOWED, "Method not allowed");
            return ESP_FAIL;
        }
    }
    else if (strcmp(subpath, "maintenance") == 0)
    {
    }
    else if (strcmp(subpath, "usr") == 0)
    {
    }
    else
    {
        cJSON_Delete(response);
        httpd_resp_send_404(req);
        return ESP_FAIL;
    }

    char *resp_str = cJSON_PrintUnformatted(response);
    httpd_resp_set_type(req, "application/json");
    httpd_resp_send(req, resp_str, strlen(resp_str));
    cJSON_Delete(response);
    free(resp_str);
    return ESP_OK;
}

/**
 * 通用的文件匹配
 */
static esp_err_t file_serving_handler(httpd_req_t *req)
{
    const char *uri;
    if (strcmp(req->uri, "/") == 0)
    {
        uri = "/index.html";
    }
    else
    {
        uri = req->uri;
    }
    ESP_LOGI(TAG, "Serving file for URI: %s", uri);

    for (size_t i = 0; i < (sizeof(embedded_files) / sizeof(embedded_files[0])); i++)
    {
        if (strcmp(uri, embedded_files[i].path) == 0)
        {
            httpd_resp_set_type(req, embedded_files[i].mime_type);
            const size_t len = embedded_files[i].end - embedded_files[i].start;
            httpd_resp_send(req, (const char *)embedded_files[i].start, len);
            return ESP_OK;
        }
    }

    ESP_LOGE(TAG, "File not found for URI: %s", req->uri);
    httpd_resp_send_404(req);
    return ESP_OK;
}

/**
 * SSE 连接建立 handler，只允许一个客户端连接。
 */
static esp_err_t sse_evt_handler(httpd_req_t *req)
{
    // add to the async request queue
    if (queue_request(req, sse_async) == ESP_OK)
    {
        return ESP_OK;
    }
    else
    {
        httpd_resp_set_status(req, "503 Busy");
        httpd_resp_sendstr(req, "<div> no workers available. server busy.</div>");
        return ESP_OK;
    }
}

static void worker_task(void *p)
{
    ESP_LOGI(TAG, "starting async req task worker");
    while (true)
    {
        xSemaphoreGive(worker_ready_count);

        httpd_async_req_t async_req;
        if (xQueueReceive(request_queue, &async_req, portMAX_DELAY))
        {
            ESP_LOGI(TAG, "invoking %s", async_req.req->uri);
            async_req.handler(async_req.req);

            if (httpd_req_async_handler_complete(async_req.req) != ESP_OK)
            {
                ESP_LOGE(TAG, "failed to complete async req");
            }
        }
    }

    ESP_LOGW(TAG, "worker stopped");
    vTaskDelete(NULL);
}

static int sse_async_work_start(void)
{
    worker_ready_count = xSemaphoreCreateCounting(1, 0);
    if (worker_ready_count == NULL)
    {
        ESP_LOGE(TAG, "Failed to create workers counting Semaphore");
        return -1;
    }

    request_queue = xQueueCreate(1, sizeof(httpd_async_req_t));
    if (request_queue == NULL)
    {
        ESP_LOGE(TAG, "Failed to create request_queue");
        vSemaphoreDelete(worker_ready_count);
        return -1;
    }

    bool success = xTaskCreate(worker_task, "async_req_worker",
                               2560,
                               (void *)0,
                               5,
                               &worker_handle);
    if (!success)
    {
        ESP_LOGE(TAG, "Failed to start asyncReqWorker");
        vSemaphoreDelete(worker_ready_count);
        return -1;
    }

    return 0;
}

int webserver_sse_client_is_connected(void)
{
    return (sse_client_connected ? 1 : 0);
}

int webserver_sse_server_send(const char *data, int len)
{
    if (!sse_client_connected)
        return -1;

    const webserver_httpd_async_req_t async_req = {
        .cmd = 0x01,
        .data = (char *)data,
        .len = len,
    };

    if (xQueueSend(sse_data_queue, &async_req, pdMS_TO_TICKS(100)) == pdTRUE)
        return len;
    else
        return 0;
}

httpd_handle_t webserver_start(void)
{
    sse_data_queue = xQueueCreate(5, sizeof(webserver_httpd_async_req_t));
    sse_state_mutex = xSemaphoreCreateMutex();
    if (sse_state_mutex == NULL || sse_data_queue == NULL)
    {
        ESP_LOGE(TAG, "Failed to create request_queue or mutex.");
        return NULL;
    }

    // 创建异步发送任务队列
    sse_async_work_start();

    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.server_port = 80;
    config.lru_purge_enable = true;
    config.uri_match_fn = httpd_uri_match_wildcard;
    config.stack_size = 8192;
    config.max_open_sockets = 1 + 1;

    ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
    if (httpd_start(&server_handle, &config) != ESP_OK)
    {
        ESP_LOGI(TAG, "Error starting server!");
        return NULL;
    }

    // 设置处理handler
    ESP_LOGI(TAG, "Registering URI handlers");

    // 通用api请求处理
    // http get, 顺序放在`/*`前面
    httpd_uri_t api_uri = {"/api/v1/*", HTTP_ANY, api_handler, NULL};
    httpd_register_uri_handler(server_handle, &api_uri);

    const httpd_uri_t sse = {"/sse", HTTP_GET, sse_evt_handler, NULL};
    httpd_register_uri_handler(server_handle, &sse);

    // 页面请求处理, 必须放最后
    httpd_uri_t file_uri = {"/*", HTTP_GET, file_serving_handler, NULL};
    httpd_register_uri_handler(server_handle, &file_uri);

    return server_handle;
}

int webserver_stop(httpd_handle_t server)
{
    return (int)httpd_stop(server);
}
